package com.lagou.sqlSession;

import com.lagou.config.BoundSql;
import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import com.lagou.utils.GenericTokenParser;
import com.lagou.utils.ParameterMapping;
import com.lagou.utils.ParameterMappingTokenHandler;
import org.apache.log4j.Logger;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Author zk
 * @Description //TODO 
 * @Date 2020/6/15
 **/
public class SimpleExecutor implements Executor {

    private static final Logger log = Logger.getLogger(SimpleExecutor.class);

    /**
     * @Author zk
     * @Description //TODO
     * @Date 16:29 2020/6/15
     * @Param [configuration, mappedStatement, params]
     * @return java.util.List<E>
     **/
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        PreparedStatement preparedStatement = prepareStatement(configuration, mappedStatement, params);
        // 5.执行SQL
        List<Object> objects = new ArrayList<>();
        try (ResultSet resultSet = preparedStatement.executeQuery()) {
            Class<?> resultType = mappedStatement.getResultType();
            while (resultSet.next()) {
                Object newInstance = resultType.getDeclaredConstructor().newInstance();
                ResultSetMetaData metaData = resultSet.getMetaData();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    String columnName = metaData.getColumnName(i);
                    Object value = resultSet.getObject(columnName);
                    // 内省，数据库字段和实体类属性一一对应
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(newInstance, handleResultType(value));
                }
                objects.add(newInstance);
            }
        }
        return (List<E>) objects;
    }


    /**
     * @Author zk
     * @Description //TODO 
     * @Date 16:30 2020/6/15
     * @Param [configuration, mappedStatement, params]
     * @return int
     **/
    @Override
    public int doUpdate(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        PreparedStatement preparedStatement = prepareStatement(configuration, mappedStatement, params);
        // 5.执行SQL
        return preparedStatement.executeUpdate();
    }

    /**
     * @Author zk
     * @Description //TODO 
     * @Date 16:30 2020/6/15
     * @Param [configuration, mappedStatement, params]
     * @return java.sql.PreparedStatement
     **/
    private PreparedStatement prepareStatement(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, NoSuchFieldException, IllegalAccessException {
        // 1.注册驱动，获取连接
        Connection connection = configuration.getDataSource().getConnection();
        // 2.获取并转换sql语句
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        // 3.获取预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
        // 4.设置参数
        Class<?> parameterType = mappedStatement.getParameterType();
        if (parameterType != null) {
            if (isPrimitive(parameterType)) {
                preparedStatement.setObject(1, params[0]);
            } else {
                List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappings.get(i);
                    String fieldName = parameterMapping.getContent();
                    // 反射
                    Field declaredField = parameterType.getDeclaredField(fieldName);
                    // 暴力访问
                    declaredField.setAccessible(true);
                    Object value = declaredField.get(params[0]);
                    preparedStatement.setObject(i + 1, handleParameterType(value));
                }
            }
        }
        log.debug("sql = " + boundSql.getSql() + ", parameter = " + params);
        return preparedStatement;
    }

   /**
    * @Author zk
    * @Description //TODO
    * @Date 16:30 2020/6/15
    * @Param [sql]
    * @return com.lagou.config.BoundSql
    **/
    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String parseSql = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        return new BoundSql(parseSql, parameterMappings);
    }

    /**
     * @Author zk
     * @Description 判断参数类型是否为 Java 原始类型
     * @Date 16:30 2020/6/15
     * @Param [parameterType]
     * @return boolean
     **/
    private boolean isPrimitive(Class<?> parameterType) throws NoSuchFieldException, IllegalAccessException {
        return parameterType == String.class || Number.class.isAssignableFrom(parameterType);
    }

    /**
     * @Author zk
     * @Description 参数类型处理
     * @Date 16:31 2020/6/15
     * @Param [value]
     * @return java.lang.Object
     **/
    private Object handleParameterType(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Date) {
            return new java.sql.Timestamp(((Date) value).getTime());
        }
        return value;
    }

    /**
     * @Author zk
     * @Description  返回值类型处理
     * @Date 16:31 2020/6/15
     * @Param [value]
     * @return java.lang.Object
     **/
    private Object handleResultType(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof java.sql.Timestamp) {
            return new Date(((java.sql.Timestamp) value).getTime());
        }
        return value;
    }

}
